using System;
using System.Collections.Generic;
using System.Xml;
using ICSharpCode.TextEditor.Formatting.TSqlFormatter.Interfaces;

namespace ICSharpCode.TextEditor.Formatting.TSqlFormatter.Formatters
{
    public class TSqlObfuscatingFormatter : ISqlTreeFormatter
    {
        private class TSqlObfuscatingFormattingState : BaseFormatterState
        {
            private const int MIN_COLOR_WORD_LENGTH = 3;

            private const int MAX_COLOR_WORD_LENGTH = 15;

            private const int MIN_LINE_LENGTH = 10;

            private const int MAX_LINE_LENGTH = 80;

            private Random _randomizer = new Random();

            private int _currentLineLength = 0;

            private int _thisLineLimit = 80;

            private int _currentColorLength = 0;

            private int _currentColorLimit = 15;

            private string _currentColor = null;

            private bool RandomizeColor { get; set; }

            private bool RandomizeLineLength { get; set; }

            internal bool BreakExpected { get; set; }

            internal bool SpaceExpectedForAnsiString { get; set; }

            internal bool SpaceExpectedForE { get; set; }

            internal bool SpaceExpectedForX { get; set; }

            internal bool SpaceExpectedForPlusMinus { get; set; }

            internal bool SpaceExpected { get; set; }

            public TSqlObfuscatingFormattingState(bool randomizeColor, bool randomizeLineLength)
                : base(randomizeColor)
            {
                RandomizeColor = randomizeColor;
                RandomizeLineLength = randomizeLineLength;
                if (RandomizeColor)
                {
                    _currentColorLimit = _randomizer.Next(3, 15);
                    _currentColor = $"#{_randomizer.Next(0, 127):x2}{_randomizer.Next(0, 127):x2}{_randomizer.Next(0, 127):x2}";
                }
                if (RandomizeLineLength)
                {
                    _thisLineLimit = _randomizer.Next(10, 80);
                }
            }

            public void BreakIfExpected()
            {
                if (BreakExpected)
                {
                    BreakExpected = false;
                    base.AddOutputLineBreak();
                    SetSpaceNoLongerExpected();
                    _currentLineLength = 0;
                    if (RandomizeLineLength)
                    {
                        _thisLineLimit = _randomizer.Next(10, 80);
                    }
                }
            }

            public void SpaceIfExpectedForAnsiString()
            {
                if (SpaceExpectedForAnsiString)
                {
                    base.AddOutputContent(" ", null);
                    SetSpaceNoLongerExpected();
                }
            }

            public void SpaceIfExpected()
            {
                if (SpaceExpected)
                {
                    base.AddOutputContent(" ", null);
                    SetSpaceNoLongerExpected();
                }
            }

            public override void AddOutputContent(string content, string htmlClassName)
            {
                if (htmlClassName != null)
                {
                    throw new NotSupportedException("Obfuscating formatter does not use html class names...");
                }
                BreakIfExpected();
                SpaceIfExpected();
                if (_currentLineLength > 0 && _currentLineLength + content.Length > _thisLineLimit)
                {
                    BreakExpected = true;
                    BreakIfExpected();
                }
                else if ((SpaceExpectedForE && content.Substring(0, 1).ToLower().Equals("e")) || (SpaceExpectedForX && content.Substring(0, 1).ToLower().Equals("x")) || (SpaceExpectedForPlusMinus && content.Substring(0, 1).Equals("+")) || (SpaceExpectedForPlusMinus && content.Substring(0, 1).Equals("-")))
                {
                    SpaceExpected = true;
                    SpaceIfExpected();
                }
                _currentLineLength += content.Length;
                if (RandomizeColor)
                {
                    int num = 0;
                    while (num < content.Length)
                    {
                        if (_currentColorLength == _currentColorLimit)
                        {
                            _currentColorLimit = _randomizer.Next(3, 15);
                            _currentColor = $"#{_randomizer.Next(0, 127):x2}{_randomizer.Next(0, 127):x2}{_randomizer.Next(0, 127):x2}";
                            _currentColorLength = 0;
                        }
                        int num2 = ((content.Length - num >= _currentColorLimit - _currentColorLength) ? (_currentColorLimit - _currentColorLength) : (content.Length - num));
                        base.AddOutputContentRaw("<span style=\"color: ");
                        base.AddOutputContentRaw(_currentColor);
                        base.AddOutputContentRaw(";\">");
                        base.AddOutputContentRaw(Utils.HtmlEncode(content.Substring(num, num2)));
                        base.AddOutputContentRaw("</span>");
                        num += num2;
                        _currentColorLength += num2;
                    }
                }
                else
                {
                    base.AddOutputContent(content, null);
                }
                SetSpaceNoLongerExpected();
            }

            private void SetSpaceNoLongerExpected()
            {
                SpaceExpected = false;
                SpaceExpectedForAnsiString = false;
                SpaceExpectedForE = false;
                SpaceExpectedForX = false;
                SpaceExpectedForPlusMinus = false;
            }

            public override void AddOutputLineBreak()
            {
                throw new NotSupportedException();
            }
        }

        public IDictionary<string, string> KeywordMapping = new Dictionary<string, string>();

        private const int MIN_CASE_WORD_LENGTH = 2;

        private const int MAX_CASE_WORD_LENGTH = 8;

        private Random _randomizer = new Random();

        private int _currentCaseLength = 0;

        private int _currentCaseLimit = 8;

        private bool _currentlyUppercase = false;

        public bool RandomizeCase { get; set; }

        public bool RandomizeColor { get; set; }

        public bool RandomizeLineLength { get; set; }

        public bool PreserveComments { get; set; }

        public bool HTMLFormatted => RandomizeColor;

        public string ErrorOutputPrefix { get; set; }

        public TSqlObfuscatingFormatter()
            : this(randomizeCase: false, randomizeColor: false, randomizeLineLength: false, preserveComments: false, subtituteKeywords: false)
        {
        }

        public TSqlObfuscatingFormatter(bool randomizeCase, bool randomizeColor, bool randomizeLineLength, bool preserveComments, bool subtituteKeywords)
        {
            RandomizeCase = randomizeCase;
            RandomizeColor = randomizeColor;
            RandomizeLineLength = randomizeLineLength;
            PreserveComments = preserveComments;
            if (subtituteKeywords)
            {
                KeywordMapping = ObfuscatingKeywordMapping.Instance;
            }
            ErrorOutputPrefix = "--WARNING! ERRORS ENCOUNTERED DURING SQL PARSING!" + Environment.NewLine;
            if (RandomizeCase)
            {
                _currentCaseLimit = _randomizer.Next(2, 8);
                _currentlyUppercase = _randomizer.Next(0, 2) == 0;
            }
        }

        public string FormatSQLTree(XmlDocument sqlTreeDoc)
        {
            TSqlObfuscatingFormattingState tSqlObfuscatingFormattingState = new TSqlObfuscatingFormattingState(RandomizeColor, RandomizeLineLength);
            if (sqlTreeDoc.SelectSingleNode(string.Format("/{0}/@{1}[.=1]", "SqlRoot", "errorFound")) != null)
            {
                tSqlObfuscatingFormattingState.AddOutputContent(ErrorOutputPrefix);
            }
            XmlNodeList nodes = sqlTreeDoc.SelectNodes(string.Format("/{0}/*", "SqlRoot"));
            return FormatSQLNodes(nodes, tSqlObfuscatingFormattingState);
        }

        public string FormatSQLTree(XmlNode fragmentNode)
        {
            TSqlObfuscatingFormattingState state = new TSqlObfuscatingFormattingState(randomizeColor: false, randomizeLineLength: false);
            return FormatSQLNodes(fragmentNode.SelectNodes("."), state);
        }

        private string FormatSQLNodes(XmlNodeList nodes, TSqlObfuscatingFormattingState state)
        {
            ProcessSqlNodeList(nodes, state);
            state.BreakIfExpected();
            return state.DumpOutput();
        }

        private void ProcessSqlNodeList(XmlNodeList rootList, TSqlObfuscatingFormattingState state)
        {
            foreach (XmlElement root in rootList)
            {
                ProcessSqlNode(root, state);
            }
        }

        private void ProcessSqlNode(XmlElement contentElement, TSqlObfuscatingFormattingState state)
        {
            switch (contentElement.Name)
            {
                case "SqlRoot":
                case "SqlStatement":
                case "Clause":
                case "SetOperatorClause":
                case "DDLProceduralBlock":
                case "DDLOtherBlock":
                case "DDLDeclareBlock":
                case "CursorDeclaration":
                case "BeginTransaction":
                case "SaveTransaction":
                case "CommitTransaction":
                case "RollbackTransaction":
                case "ContainerOpen":
                case "ContainerClose":
                case "WhileLoop":
                case "IfStatement":
                case "SelectionTarget":
                case "ContainerContentBody":
                case "CTEWithClause":
                case "PermissionsBlock":
                case "PermissionsDetail":
                case "MergeClause":
                case "MergeTarget":
                case "Input":
                case "BooleanExpression":
                case "LowerBound":
                case "UpperBound":
                case "ContainerSingleStatementBody":
                case "ContainerMultiStatementBody":
                case "MergeAction":
                case "PermissionsTarget":
                case "PermissionsRecipient":
                case "DDLWith":
                case "MergeCondition":
                case "MergeThen":
                case "JoinOn":
                case "CTEAlias":
                case "ElseClause":
                case "DDLAsBlock":
                case "CursorForBlock":
                case "TriggerCondition":
                case "CursorForOptions":
                case "CTEAsBlock":
                case "DDLReturns":
                case "MergeUsing":
                case "MergeWhen":
                case "Between":
                case "BeginEndBlock":
                case "TryBlock":
                case "CatchBlock":
                case "CaseStatement":
                case "When":
                case "Then":
                case "CaseElse":
                case "And":
                case "Or":
                    ProcessSqlNodeList(contentElement.SelectNodes("*"), state);
                    break;
                case "DDLDetailParens":
                case "FunctionParens":
                case "InParens":
                case "DDLParens":
                case "ExpressionParens":
                case "SelectionTargetParens":
                    state.SpaceExpected = false;
                    state.AddOutputContent("(");
                    ProcessSqlNodeList(contentElement.SelectNodes("*"), state);
                    state.SpaceExpected = false;
                    state.SpaceExpectedForAnsiString = false;
                    state.AddOutputContent(")");
                    break;
                case "WhiteSpace":
                    break;
                case "MultiLineComment":
                    if (PreserveComments)
                    {
                        state.SpaceExpected = false;
                        state.AddOutputContent("/*" + contentElement.InnerText + "*/");
                    }
                    break;
                case "SingleLineComment":
                    if (PreserveComments)
                    {
                        state.SpaceExpected = false;
                        state.AddOutputContent("--" + contentElement.InnerText.Replace("\r", "").Replace("\n", ""));
                        state.BreakExpected = true;
                    }
                    break;
                case "SingleLineCommentCStyle":
                    if (PreserveComments)
                    {
                        state.SpaceExpected = false;
                        state.AddOutputContent("//" + contentElement.InnerText.Replace("\r", "").Replace("\n", ""));
                        state.BreakExpected = true;
                    }
                    break;
                case "BatchSeparator":
                    state.BreakExpected = true;
                    ProcessSqlNodeList(contentElement.SelectNodes("*"), state);
                    state.BreakExpected = true;
                    break;
                case "String":
                    state.SpaceIfExpectedForAnsiString();
                    state.SpaceExpected = false;
                    state.AddOutputContent("'" + contentElement.InnerText.Replace("'", "''") + "'");
                    state.SpaceExpectedForAnsiString = true;
                    break;
                case "NationalString":
                    state.AddOutputContent("N'" + contentElement.InnerText.Replace("'", "''") + "'");
                    state.SpaceExpectedForAnsiString = true;
                    break;
                case "BracketQuotedName":
                    state.SpaceExpected = false;
                    state.AddOutputContent("[" + contentElement.InnerText.Replace("]", "]]") + "]");
                    break;
                case "QuotedString":
                    state.SpaceExpected = false;
                    state.AddOutputContent("\"" + contentElement.InnerText.Replace("\"", "\"\"") + "\"");
                    break;
                case "Comma":
                case "Period":
                case "Semicolon":
                case "ScopeResolutionOperator":
                case "Asterisk":
                case "EqualsSign":
                case "OtherOperator":
                    state.SpaceExpected = false;
                    state.AddOutputContent(contentElement.InnerText);
                    break;
                case "CompoundKeyword":
                    state.AddOutputContent(FormatKeyword(contentElement.Attributes["simpleText"].Value));
                    state.SpaceExpected = true;
                    break;
                case "Label":
                    state.AddOutputContent(contentElement.InnerText);
                    state.BreakExpected = true;
                    break;
                case "OtherKeyword":
                case "AlphaOperator":
                case "DataTypeKeyword":
                case "PseudoName":
                case "BinaryValue":
                    state.AddOutputContent(FormatKeyword(contentElement.InnerText));
                    state.SpaceExpected = true;
                    break;
                case "NumberValue":
                    state.AddOutputContent(FormatKeyword(contentElement.InnerText));
                    if (!contentElement.InnerText.ToLowerInvariant().Contains("e"))
                    {
                        state.SpaceExpectedForE = true;
                        if (contentElement.InnerText.Equals("0"))
                        {
                            state.SpaceExpectedForX = true;
                        }
                    }
                    break;
                case "MonetaryValue":
                    if (!contentElement.InnerText.Substring(0, 1).Equals("$"))
                    {
                        state.SpaceExpected = false;
                    }
                    state.AddOutputContent(contentElement.InnerText);
                    if (contentElement.InnerText.Length == 1)
                    {
                        state.SpaceExpectedForPlusMinus = true;
                    }
                    break;
                case "Other":
                case "FunctionKeyword":
                    state.AddOutputContent(contentElement.InnerText);
                    state.SpaceExpected = true;
                    break;
                default:
                    throw new Exception("Unrecognized element in SQL Xml!");
            }
        }

        private string FormatKeyword(string keyword)
        {
            if (!KeywordMapping.TryGetValue(keyword, out var value))
            {
                value = keyword;
            }
            if (RandomizeCase)
            {
                return GetCaseRandomized(value);
            }
            return value;
        }

        private string GetCaseRandomized(string outputKeyword)
        {
            char[] array = outputKeyword.ToCharArray();
            for (int i = 0; i < array.Length; i++)
            {
                if (_currentCaseLength == _currentCaseLimit)
                {
                    _currentCaseLimit = _randomizer.Next(2, 8);
                    _currentlyUppercase = _randomizer.Next(0, 2) == 0;
                    _currentCaseLength = 0;
                }
                array[i] = (_currentlyUppercase ? char.ToUpperInvariant(array[i]) : char.ToLowerInvariant(array[i]));
                _currentCaseLength++;
            }
            return new string(array);
        }
    }
}
